/**
 * @file custommenubar.h
 * @brief
 *
 * 
 *
 * @author 1823419256@qq.com
 * @version 1.0
 * @date 2011
*/

#ifndef MYBOOK_CTRLS_CUSTOMMENUBAR_H_
#define MYBOOK_CTRLS_CUSTOMMENUBAR_H_

#include "aspectfactory.h"

class CCustomMenuBar : public CWindowImpl<CCustomMenuBar, CWindow>, CMouseHover<CCustomMenuBar>
{
public:
	DECLARE_WND_CLASS(_T("MyCustomMenuBar"))
	typedef CWindowImpl<CCustomMenuBar, CWindow> baseClass;

	explicit CCustomMenuBar(CAspectAbstractFactory* pAspectFactory)
		: m_pAspectFactory(pAspectFactory)
		, m_bMouseTracking(FALSE)
		, m_nHoverMenu(-1)
		, m_nPopupMenu(-1)
	{
		ATLASSERT(m_pAspectFactory);
	}

	HWND Create(HWND hWndParent, RECT& rect, int nID = 0, DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP, LPCTSTR szWindowName = NULL)
	{
		m_hParent = hWndParent;
		m_mnuMenu.LoadMenu(nID);

		return baseClass::Create(hWndParent, rect, szWindowName, dwStyle, 0, nID);
	}

public:
	int GetMenuHeight() const { return 20; }

protected:

public:
	BEGIN_MSG_MAP(CCustomMenuBar)
		CHAIN_MSG_MAP(CMouseHover<CCustomMenuBar>)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_COMMAND, OnCommand)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
		MESSAGE_HANDLER(WM_MOUSEENTER, OnMouseEnter)
		MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		MESSAGE_HANDLER(GetGetBarMessage(), OnInternalGetBar)
		MESSAGE_HANDLER(WM_FORWARDMSG, OnForwardMsg)
	ALT_MSG_MAP(1)
		MESSAGE_HANDLER(WM_MOUSEMOVE, OnHookMouseMove)
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 0;
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 0;
	}

	LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		::PostMessage(GetParent(), uMsg, wParam, lParam);
		return 0;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return 0;
	}

	LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return FALSE;
	}

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		CPaintDC dc(m_hWnd);

		dc.SelectFont(m_pAspectFactory->GetTextFont());
		dc.SetBkMode(TRANSPARENT);

		dc.SetTextColor(m_pAspectFactory->GetTextColor());

		CBrush br;
		br.CreateSolidBrush(m_pAspectFactory->GetBackColor());
		dc.SelectBrush(br);

		CClientRect rc(m_hWnd);
		int nRightBorder = rc.right - 10;

		CString strMenuString;
		int nCount = (int)m_mnuMenu.GetMenuItemCount();
		m_vecItemRect.resize(nCount);

		for (int i = nCount - 1; i >= 0; --i)
		{
			m_mnuMenu.GetMenuString(i, strMenuString, MF_BYPOSITION);

			CSize szDraw;
			GetTextExtentPoint(dc, strMenuString, strMenuString.GetLength(), &szDraw);

			CRect rcItem(nRightBorder - szDraw.cx, 0, nRightBorder, rc.Height());
			rcItem.left -= 10;
			rcItem.DeflateRect(0, 2);

			if (m_nHoverMenu == i || m_nPopupMenu == i)
			{
				CSaveDC sdc(dc);

				CPen pen;
				pen.CreatePen(PS_SOLID, 1, m_pAspectFactory->GetBackColorAlter());
				dc.SelectPen(pen);

				dc.SetTextColor(m_pAspectFactory->GetTextHoverColor());
				dc.Rectangle(rcItem);

				dc.DrawText(strMenuString, -1, rcItem, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
			}
			else
				dc.DrawText(strMenuString, -1, rcItem, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

			nRightBorder = rcItem.left - 10;

			m_vecItemRect[i] = rcItem;
		}

		return 0;
	}

	LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		CDCHandle dc = (HDC)wParam;

		CRect rect;
		GetClientRect(rect);

		CPoint pt(0, 0);
		MapWindowPoints(m_hParent, &pt, 1);
		OffsetWindowOrgEx(dc, pt.x, pt.y, &pt);

		CRgn rgn;
		rgn.CreateRectRgnIndirect(&rect);
		dc.SelectClipRgn(rgn);

		::SendMessage(m_hParent, WM_ERASEBKGND, (WPARAM)(HDC)dc, 0);
		::SendMessage(m_hParent, WM_PAINT, (WPARAM)(HDC)dc, 0);
		::SetWindowOrgEx(dc, pt.x, pt.y, NULL);

		return TRUE;
	}

	LRESULT OnMouseEnter(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		m_bMouseTracking = TRUE;
		Invalidate();

		return 0;
	}

	LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		m_bMouseTracking = FALSE;
		m_nHoverMenu = -1;

		Invalidate();

		return 0;
	}

	LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
	{
		CPoint pt(lParam);

		int nCurHover = -1;
		for (int i = 0; i < (int)m_vecItemRect.size(); ++i)
		{
			if (m_vecItemRect[i].PtInRect(pt))
			{
				nCurHover = i;
				break;
			}
		}

		if (nCurHover != m_nHoverMenu)
		{
			if (m_nHoverMenu >= 0)
				InvalidateRect(m_vecItemRect[m_nHoverMenu]);
			
			if (nCurHover >= 0)
				InvalidateRect(m_vecItemRect[nCurHover]);

			m_nHoverMenu = nCurHover;
		}

		return 0L;
	}

	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if (m_nHoverMenu == -1 || m_nHoverMenu >= (int)m_mnuMenu.GetMenuItemCount())
			return 0;

		CMenuHandle mnu = m_mnuMenu.GetSubMenu(m_nHoverMenu);
		m_nPopupMenu = m_nHoverMenu;

		CRect rcMenu = m_vecItemRect[m_nHoverMenu];
		ClientToScreen(&rcMenu);

		TPMPARAMS tps = { sizeof(TPMPARAMS) };
		tps.rcExclude = rcMenu;

		// hook
		m_hMsgHook = SetWindowsHookEx(WH_MSGFILTER, MenuMsgFilter, ModuleHelper::GetModuleInstance(), GetCurrentThreadId());
		
		// track menu
		mnu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL, rcMenu.left, rcMenu.top, m_hWnd, &tps);
		
		// unhook
		UnhookWindowsHookEx(m_hMsgHook);
		m_hMsgHook = NULL;
		
		if (m_nPopupMenu != -1)
			InvalidateRect(m_vecItemRect[m_nPopupMenu]);
		m_nPopupMenu = -1;

		return 0L;
	}

	LRESULT OnInternalGetBar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		if ((LPVOID)wParam != NULL)
			*((DWORD*)wParam) = GetCurrentProcessId();

		if (IsWindowVisible())
			return (LRESULT)(this);
		else
			return NULL;
	}

	LRESULT OnForwardMsg(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
	{
		LPMSG pMsg = (LPMSG)lParam;
		LRESULT lRet = 0;
		ProcessWindowMessage(pMsg->hwnd, pMsg->message, pMsg->wParam, pMsg->lParam, lRet, 1);
		return lRet;
	}

	LRESULT OnHookMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
	{
		bHandled = FALSE;

		static POINT s_point = { -1, -1 };
		DWORD dwPoint = ::GetMessagePos();
		POINT point = { GET_X_LPARAM(dwPoint), GET_Y_LPARAM(dwPoint) };

		if (::WindowFromPoint(point) == m_hWnd)
		{
			ScreenToClient(&point);
			int nHit = -1;
			for (int i = 0; i < (int)m_vecItemRect.size(); ++i)
			{
				if (m_vecItemRect[i].PtInRect(point))
				{
					nHit = i;
					break;
				}
			}

			if ((point.x != s_point.x || point.y != s_point.y) && nHit >= 0 && nHit != m_nPopupMenu && m_nPopupMenu != -1)
			{
				::PostMessage(m_hWnd, WM_KEYDOWN, VK_ESCAPE, 0L);
				::PostMessage(m_hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y));
				::PostMessage(m_hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(point.x, point.y));

				//bHandled = TRUE;
			}
		}

		return 0;
	}

protected:
	static LRESULT CALLBACK MenuMsgFilter(int nCode, WPARAM wParam, LPARAM lParam)
	{
		MSG* pMsg = (MSG*)lParam;

		if (nCode == MSGF_MENU && pMsg->message != GetGetBarMessage())
		{
			DWORD dwPID = 0;
			CCustomMenuBar* pThis = (CCustomMenuBar*)SendMessage(pMsg->hwnd, GetGetBarMessage(), (WPARAM)&dwPID, 0);

			if (pThis != NULL && dwPID == GetCurrentProcessId())
			{
				if(::IsWindow(pThis->m_hWnd))
					pThis->SendMessage(WM_FORWARDMSG, 0, (LPARAM)pMsg);
			}
		}
		
		return CallNextHookEx(m_hMsgHook, nCode, wParam, lParam);
	}

	static UINT GetGetBarMessage()
	{
		static UINT uGetBarMessage = 0;
		if (uGetBarMessage == 0)
		{
			CStaticDataInitCriticalSectionLock lock;
			if (FAILED(lock.Lock()))
			{
				return 0;
			}

			if (uGetBarMessage == 0)
				uGetBarMessage = ::RegisterWindowMessage(_T("MyCustomMenuBarGet"));

			lock.Unlock();
		}

		return uGetBarMessage;
	}

protected:
	HWND	m_hParent;
	CMenu	m_mnuMenu;
	std::vector<CRect> m_vecItemRect;

	BOOL	m_bMouseTracking;
	int		m_nHoverMenu;

	int		m_nPopupMenu;
	__declspec(thread) static HHOOK m_hMsgHook;

protected:
	boost::scoped_ptr<CAspectAbstractFactory> m_pAspectFactory;
};

#endif // MYBOOK_CTRLS_CUSTOMMENUBAR_H_